﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using Krilib.Data;
using Krilib.Control;
using Krilib.Bridges;

namespace Krile.Forms.MainForm.Controls.InputMethodManager
{
    public partial class InputMethodManager : UserControl, IInputControlManager
    {

        ulong _inReplyToUID = 0;
        public ulong InReplyToUID
        {
            get { return _inReplyToUID; }
            private set
            {
                _inReplyToUID = value;
                ReplyInfoBar.Visible = _inReplyToUID != 0;
                ServiceLabel.Enabled = _inReplyToUID == 0;
                UpdateServiceSelText();
            }
        }

        //save state
        Service[] prevSelected = null;
        /// <summary>
        /// Set in reply to uid
        /// </summary>
        /// <remarks>
        /// If you set zero to uid,it calls ClearReplyTo() method.
        /// </remarks>
        /// <param name="uid">Message uid</param>
        public void SetReplyTo(ulong uid)
        {
            if (uid == 0)
            {
                ClearReplyTo();
                return;
            }

            if (!Core.StatusManager.IdExists(uid))
                throw new ArgumentOutOfRangeException("uid", "UID " + uid.ToString() + " isn't exists in Krile status manager.");

            if (InReplyToUID == 0)
                prevSelected = SelectedServices;
            InReplyToUID = uid;
            SelectedServices = new Service[] { Core.StatusManager.GetStatus(uid).ParentService };
            if (OnShowRequired != null)
                OnShowRequired.Invoke(this, new EventArgs());
        }

        /// <summary>
        /// initialize replying state
        /// </summary>
        public void ClearReplyTo()
        {
            if (InReplyToUID != 0)
                SelectedServices = prevSelected;
            prevSelected = null;
            InReplyToUID = 0;
        }

        /// <summary>
        /// Examine current input method's type
        /// </summary>
        /// <param name="type">test type</param>
        /// <returns>equals=true</returns>
        public bool CheckCurInputMethodIs(Type type)
        {
            return type.Equals(curInputMethod);
        }

        /// <summary>
        /// Get current input method's id.
        /// </summary>
        /// <param name="id">examine id</param>
        /// <remarks>
        /// If I haven't any method now, I always return false.
        /// </remarks>
        public bool CheckCurInputMethodIs(string id)
        {
            if (curInputMethod == null)
                return false;
            else
                return curInputMethod.IdentifyString == id;
        }

        /// <summary>
        /// Check existing current input method
        /// </summary>
        /// <returns></returns>
        public bool IsCurInputMethodExisted()
        {
            return curInputMethod != null;
        }

        /// <summary>
        /// Set focus to inputter
        /// </summary>
        public void SetFocus()
        {
            if (curInputMethod != null)
                curInputMethod.FocusToInput();
        }

        /// <summary>
        /// Overwrite input text
        /// </summary>
        /// <param name="text">set text</param>
        public void SetInputText(string text)
        {
            SetInputText(text, false);
        }

        /// <summary>
        /// Overwrite input text
        /// </summary>
        /// <param name="text">set text</param>
        /// <param name="raiseInvalidOperationException">if internal input method reference is null, I raise it.</param>
        public void SetInputText(string text, bool raiseInvalidOperationException)
        {
            if (curInputMethod == null)
            {
                if (raiseInvalidOperationException)
                    throw new InvalidOperationException("Current input method is null.");
                else
                    return;
            }
            curInputMethod.InputText = text;
        }

        /// <summary>
        /// Get current inputted text
        /// </summary>
        /// <returns></returns>
        public string GetInputText()
        {
            if (curInputMethod == null)
                return null;
            else
                return curInputMethod.InputText;
        }

        /// <summary>
        /// Set extra data to current input method
        /// </summary>
        /// <remarks>
        /// For security reason, I set this prop only when id matches current method's id.
        /// You should examine current id before call this method.
        /// </remarks>
        /// <param name="id"></param>
        /// <param name="extraData"></param>
        public void SetExtraData(string id, object extraData)
        {
            if (curInputMethod == null)
                throw new InvalidOperationException("Current input method is null.");
            if (curInputMethod.IdentifyString != id)
                throw new ArgumentException("Id isn't match.Krile can't execute this method.", id);
            curInputMethod.ExtraData = extraData;
        }

        private IInputMethod curInputMethod = null;

        public bool ShowChangeVLocButton
        {
            get { return ChangeVLoc.Visible; }
            set { ChangeVLoc.Visible = value; }
        }

        public event EventHandler OnCloseRequired;

        public event EventHandler OnShowRequired;

        public event EventHandler OnChangeVLocRequired;

        public event EventHandler OnSelectedServicesChanged;

        private Service[] _selectedServices;
        public Service[] SelectedServices
        {
            get
            {
                return _selectedServices;
            }
            set
            {
                _selectedServices = value;
                UpdateServiceSelText();
                UpdateCurrentControl();
                if (OnSelectedServicesChanged != null)
                    OnSelectedServicesChanged.Invoke(this, new EventArgs());
            }
        }

        public InputMethodManager()
        {
            InitializeComponent();
            _selectedServices = null;
            CloseButton.FlatAppearance.MouseOverBackColor = Color.FromArgb(150, Color.Pink);
            CloseButton.FlatAppearance.MouseDownBackColor = Color.FromArgb(255, Color.Pink);
            CancelReply.FlatAppearance.MouseOverBackColor = Color.FromArgb(90, Color.Red);
            CancelReply.FlatAppearance.MouseDownBackColor = Color.FromArgb(180, Color.Red);
            ChangeVLoc.FlatAppearance.MouseOverBackColor = Color.FromArgb(150, Color.SkyBlue);
            ChangeVLoc.FlatAppearance.MouseDownBackColor = Color.FromArgb(255, Color.SkyBlue);
        }

        public void Init()
        {
            DisposeCurInputMethod();
        }

        private void TitlebarPanel_Paint(object sender, PaintEventArgs e)
        {
            using (var lgb = new LinearGradientBrush(TitlebarPanel.ClientRectangle, Color.RoyalBlue, Color.CornflowerBlue, 90f))
                e.Graphics.FillRectangle(lgb, TitlebarPanel.ClientRectangle);
        }

        private void ServiceLabel_Resize(object sender, EventArgs e)
        {
            ServicePanel.Width = ServiceLabel.Width;
        }

        Dialogs.Editor.SelectService svcSelector = null;
        private void ServiceLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (!ServiceLabel.Enabled) return;
            ServicePanel.BackColor = Color.CornflowerBlue;
            ServicePanel.BorderStyle = BorderStyle.FixedSingle;
            svcSelector = new Krile.Forms.Dialogs.Editor.SelectService();
            svcSelector.FormClosed += new FormClosedEventHandler(svcSelector_FormClosed);
            if (_selectedServices != null)
                svcSelector.SelectedServices = _selectedServices;
            svcSelector.Location = ServicePanel.PointToScreen(new Point(-1, ServicePanel.Height - 1));
            svcSelector.Show();
        }

        void svcSelector_FormClosed(object sender, FormClosedEventArgs e)
        {
            SelectedServices = svcSelector.SelectedServices;
            svcSelector.Dispose();
            ServicePanel.BorderStyle = BorderStyle.None;
            ServicePanel.BackColor = Color.Transparent;
        }

        void UpdateServiceSelText()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(UpdateServiceSelText));
            }
            else
            {
                if (SelectedServices == null || SelectedServices.Length == 0)
                    ServiceLabel.Text = "Unselected";
                else if (SelectedServices.Length == 1)
                    ServiceLabel.Text = SelectedServices[0].Name;
                else if (SelectedServices.Length == Core.PluginManager.GetServiceEnumerator().Count<Service>())
                    ServiceLabel.Text = "All";
                else
                    ServiceLabel.Text = "Partial";
            }
            if (!ServiceLabel.Enabled)
                ServiceLabel.Text += " [LOCKED]";
            else
                ServiceLabel.Text += "...";
        }

        void UpdateCurrentControl()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(UpdateCurrentControl));
            }
            else
            {
                if (SelectedServices == null || SelectedServices.Length == 0)
                {
                    if (curInputMethod == null) return;
                    DisposeCurInputMethod();
                    return;
                }
                else if (SelectedServices.Length == 1)
                {
                    if (curInputMethod != null && curInputMethod.IdentifyString == SelectedServices[0].ParentAgent.InputMethodId) return;
                    DisposeCurInputMethod();
                    curInputMethod = Core.ControlManager.LookupMethod(SelectedServices[0].ParentAgent.InputMethodId);
                }
                else
                {
                    if (curInputMethod != null && curInputMethod.IdentifyString == null) return;
                    DisposeCurInputMethod();
                    curInputMethod = new DefaultInput();
                }
                InitCurInputMethod();
            }
        }

        private void DisposeCurInputMethod()
        {
            if (curInputMethod == null) return;
            curInputMethod.Clear();
            ((UserControl)curInputMethod).Dispose();
            curInputMethod = null;
            UnselectedWarning.Visible = true;
        }

        private void InitCurInputMethod()
        {
            UnselectedWarning.Visible = false;
            Subsystem.Debugger.Assert(curInputMethod is UserControl, "INPUT METHOD can't cast to UserControl");
            curInputMethod.PostCallback = new MethodInvoker(PostCallbackMethod);
            ((UserControl)curInputMethod).Parent = MainPanel;
            ((UserControl)curInputMethod).Dock = DockStyle.Fill;
        }

        /// <summary>
        /// callback method after posted
        /// </summary>
        private void PostCallbackMethod()
        {
            this.Enabled = false;
            PostInvokerDelg pinv = new PostInvokerDelg(PostInvoker);
            pinv.BeginInvoke(curInputMethod.InputText, InReplyToUID, curInputMethod.ExtraData,
                (iar) =>
                {
                    ((PostInvokerDelg)iar.AsyncState).EndInvoke(iar);
                    this.Invoke(new Action(AfterPost));
                }, pinv);

        }

        delegate void PostInvokerDelg(string text, ulong replyTo, object affix);
        private void PostInvoker(String text, ulong replyTo, object affix)
        {
            if (SelectedServices != null)
            {
                var sels = SelectedServices.Clone() as Service[];
                foreach (var s in sels)
                {
                    if (s.ParentAgent.Status.Updatable)
                    {
                        this.Enabled = false;
                        s.ParentAgent.Status.Update(text, replyTo, affix);
                    }
                }
            }
        }

        private void AfterPost()
        {
            this.Enabled = true;
            this.Clear();
            this.ClearReplyTo();
            if (Core.Config.Behavior.ClosePostboxAfterPost)
                this.CloseButton_Click(null, null);
        }

        private void ReplyTextRenderer_Paint(object sender, PaintEventArgs e)
        {
            if (InReplyToUID != 0)
            {
                StringBuilder repDTB = new StringBuilder();
                if (Core.StatusManager.IdExists(InReplyToUID))
                {
                    var s = Core.StatusManager.GetStatus(InReplyToUID);
                    if (s.user.name != null)
                        repDTB.Append(s.user.name + ":");
                    else
                        repDTB.Append("@" + s.user.id + ":");
                    if (String.IsNullOrEmpty(s.text.Original))
                        repDTB.Append("(Text is empty)");
                    else
                        repDTB.Append(s.text.Original);
                }
                else
                {
                    repDTB.Append("(ERROR:Status not found)");
                }
                TextRenderer.DrawText(e.Graphics,
                    ">> " + repDTB.ToString(), this.Font, ReplyTextRenderer.ClientRectangle,
                    SystemColors.ControlText, TextFormatFlags.EndEllipsis |
                    TextFormatFlags.NoPrefix | TextFormatFlags.SingleLine |
                    TextFormatFlags.VerticalCenter);
            }
        }

        internal void Clear()
        {
            if (curInputMethod != null)
                curInputMethod.Clear();
        }

        internal void Initialize()
        {
            List<Service> svcs = new List<Service>(Core.PluginManager.GetServiceEnumerator());
            SelectedServices = svcs.ToArray();
        }

        private void ChangeVLoc_Click(object sender, EventArgs e)
        {
            if (OnChangeVLocRequired != null)
                OnChangeVLocRequired.Invoke(this, e);
        }

        private void CloseButton_Click(object sender, EventArgs e)
        {
            if (OnCloseRequired != null)
                OnCloseRequired.Invoke(this, e);
            Clear();
            if (Core.Config.Behavior.ClearReplyOnClosePostBox)
                ClearReplyTo();
        }

        private void CancelReply_Click(object sender, EventArgs e)
        {
            ClearReplyTo();
        }
    }
}
 